package com.hmall.pay.service.impl;

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.api.user.UserClient;
import com.hmall.common.constant.MqConstants;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.utils.BeanUtils;
import com.hmall.common.utils.UserContext;
import com.hmall.pay.domain.dto.PayApplyDTO;
import com.hmall.pay.domain.dto.PayOrderFormDTO;
import com.hmall.pay.domain.po.PayOrder;
import com.hmall.pay.enums.PayStatus;
import com.hmall.pay.mapper.PayOrderMapper;
import com.hmall.pay.service.IPayOrderService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

/**
 * <p>
 * 支付订单 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2023-05-16
 */
@Service
@RequiredArgsConstructor
public class PayOrderServiceImpl extends ServiceImpl<PayOrderMapper, PayOrder> implements IPayOrderService {

	private final UserClient userClient;
	// private final TradeClient tradeClient;

	private final AmqpTemplate amqpTemplate;

	@Override
	public String applyPayOrder(PayApplyDTO applyDTO) {
		// 1.幂等性校验
		PayOrder payOrder = checkIdempotent(applyDTO);
		// 2.返回结果
		return payOrder.getId().toString();
	}

	@Override
	// @Transactional
	@GlobalTransactional
	public void tryPayOrderByBalance(PayOrderFormDTO payOrderFormDTO) {
		// 1.查询支付单
		PayOrder po = getById(payOrderFormDTO.getId());
		// 2.判断状态
		if (!PayStatus.WAIT_BUYER_PAY.equalsValue(po.getStatus())) {
			// 订单不是未支付，状态异常
			throw new BizIllegalException("交易已支付或关闭！");
		}
		// 3.尝试扣减余额
		userClient.deductMoney(payOrderFormDTO.getPw(), po.getAmount(), UserContext.getUser());
		// 4.修改支付单状态
		boolean success = markPayOrderSuccess(payOrderFormDTO.getId(), LocalDateTime.now());
		if (!success) {
			throw new BizIllegalException("交易已支付或关闭！");
		}
		// 5.修改订单状态
		// tradeClient.markOrderPaySuccess(po.getBizOrderNo());
		// int i = 1 / 0;
		// 使用 MQ
		amqpTemplate.convertAndSend(MqConstants.PAY_EXCHANGE_NAME, MqConstants.PAY_SUCCESS_KEY, po.getBizOrderNo());
	}

	public boolean markPayOrderSuccess(Long id, LocalDateTime successTime) {
		return lambdaUpdate().set(PayOrder::getStatus, PayStatus.TRADE_SUCCESS.getValue()).set(PayOrder::getPaySuccessTime,
				successTime
			).eq(PayOrder::getId, id)
			// 支付状态的乐观锁判断
			.in(PayOrder::getStatus, PayStatus.NOT_COMMIT.getValue(), PayStatus.WAIT_BUYER_PAY.getValue()).update();
	}


	private PayOrder checkIdempotent(PayApplyDTO applyDTO) {
		// 1.首先查询支付单
		PayOrder oldOrder = queryByBizOrderNo(applyDTO.getBizOrderNo());
		// 2.判断是否存在
		if (oldOrder == null) {
			// 不存在支付单，说明是第一次，写入新的支付单并返回
			PayOrder payOrder = buildPayOrder(applyDTO);
			payOrder.setPayOrderNo(IdWorker.getId());
			save(payOrder);
			return payOrder;
		}
		// 3.旧单已经存在，判断是否支付成功
		if (PayStatus.TRADE_SUCCESS.equalsValue(oldOrder.getStatus())) {
			// 已经支付成功，抛出异常
			throw new BizIllegalException("订单已经支付！");
		}
		// 4.旧单已经存在，判断是否已经关闭
		if (PayStatus.TRADE_CLOSED.equalsValue(oldOrder.getStatus())) {
			// 已经关闭，抛出异常
			throw new BizIllegalException("订单已关闭");
		}
		// 5.旧单已经存在，判断支付渠道是否一致
		if (!StringUtils.equals(oldOrder.getPayChannelCode(), applyDTO.getPayChannelCode())) {
			// 支付渠道不一致，需要重置数据，然后重新申请支付单
			PayOrder payOrder = buildPayOrder(applyDTO);
			payOrder.setId(oldOrder.getId());
			payOrder.setQrCodeUrl("");
			updateById(payOrder);
			payOrder.setPayOrderNo(oldOrder.getPayOrderNo());
			return payOrder;
		}
		// 6.旧单已经存在，且可能是未支付或未提交，且支付渠道一致，直接返回旧数据
		return oldOrder;
	}

	private PayOrder buildPayOrder(PayApplyDTO payApplyDTO) {
		// 1.数据转换
		PayOrder payOrder = BeanUtils.toBean(payApplyDTO, PayOrder.class);
		// 2.初始化数据
		payOrder.setPayOverTime(LocalDateTime.now().plusMinutes(120L));
		payOrder.setStatus(PayStatus.WAIT_BUYER_PAY.getValue());
		payOrder.setBizUserId(UserContext.getUser());
		return payOrder;
	}

	public PayOrder queryByBizOrderNo(Long bizOrderNo) {
		return lambdaQuery().eq(PayOrder::getBizOrderNo, bizOrderNo).one();
	}
}
